/*
 * Open Source RFID Access Controller
 *
 * 4/3/2011 v1.32
 * Last build test with Arduino v00.21
 * Arclight - arclight@23.org
 * Danozano - danozano@gmail.com
 *
 * Notice: This is free software and is probably buggy. Use it at
 * at your own peril.  Use of this software may result in your
 * doors being left open, your stuff going missing, or buggery by
 * high seas pirates. No warranties are expressed on implied.
 * You are warned.
 *
 *
 * For latest downloads, including Eagle CAD files for the hardware, check out
 * http://code.google.com/p/open-access-control/downloads/list
 *
 * Latest update moves strings to PROGMEM to free up memory and adds a 
 * console password feature.
 * 
 *
 * This program interfaces the Arduino to RFID, PIN pad and all
 * other input devices using the Wiegand-26 Communications
 * Protocol. It is recommended that the keypad inputs be
 * opto-isolated in case a malicious user shorts out the 
 * input device.
 * Outputs go to a Darlington relay driver array for door hardware/etc control.
 * Analog inputs are used for alarm sensor monitoring.  These should be
 * isolated as well, since many sensors use +12V. Note that resistors of
 * different values can be used on each zone to detect shorting of the sensor
 * or wiring.
 *
 * Version 1.00+ of the hardware implements these features and uses the following pin 
 * assignments on a standard Arduino Duemilanova or Uno:
 *
 * Relay outpus on digital pins 6,7,8,9
 * DS1307 Real Time Clock (I2C):A4 (SDA), A5 (SCL)
 * Analog pins (for alarm):A0,A1,A2,A3 
 * Reader 1: pins 2,3
 * Reader 2: pins 4,5
 * Ethernet: pins 10,11,12,13 (Not connected to the board, reserved for the Ethernet shield)
 *
 * Quickstart tips: 
 * Set the console password(PRIVPASSWORD) value to a numeric DEC or HEX value.
 * Define the static user list by swiping a tag and copying the value received into the #define values shown below 
 * Compile and upload the code, then log in via serial console at 57600,8,N,1
 *
 */

#include <Wire.h>         // Needed for I2C Connection to the DS1307 date/time chip
#include <EEPROM.h>       // Needed for saving to non-voilatile memory on the Arduino.
#include <avr/pgmspace.h> // Allows data to be stored in FLASH instead of RAM

/*
#include <Ethernet.h>   // Ethernet stuff, comment out if not used.
#include <SPI.h>          
#include <Server.h>
#include <Client.h>
*/

#include <DS1307.h>       // DS1307 RTC Clock/Date/Time chip library
#include <WIEGAND26.h>    // Wiegand 26 reader format libary
#include <PCATTACH.h>     // Pcint.h implementation, allows for >2 software interupts.


/* Static user List - Implemented as an array for testing and access override 
 */                               

#define DEBUG 2                         // Set to 2 for display of raw tag numbers in log files, 1 for only denied, 0 for never.               

#define gonzo   0x1234                  // Name and badge number in HEX. We are not using checksums or site ID, just the whole
#define snake   0x1234                  // output string from the reader.
#define satan   0x1234
//const long  superUserList[] = { gonzo, snake, satan};  // Super user table (cannot be changed by software)

#define PRIVPASSWORD 0x1234             // Console "priveleged mode" password

#define PRIVPASSWORD 99887766
#define testUser1TagNName 67108863
#define testUser2TagNName 1073741823
#define testUser3TagNName 4294967295
const unsigned long superUserList[] = { testUser1TagNName, testUser2TagNName, testUser3TagNName } ; //looks like input new users as a super user list

#define DOORDELAY 5000                  // How long to open door lock once access is granted. (2500 = 2.5s)
#define SENSORTHRESHOLD 100             // Analog sensor change that will trigger an alarm (0..255)

#define EEPROM_ALARM 0                  // EEPROM address to store alarm triggered state between reboots (0..511)
#define EEPROM_ALARMARMED 1             // EEPROM address to store alarm armed state between reboots
#define EEPROM_ALARMZONES 20            // Starting address to store "normal" analog values for alarm zone sensor reads.
#define KEYPADTIMEOUT 5000              // Timeout for pin pad entry. Users on keypads can enter commands after reader swipe.

#define EEPROM_FIRSTUSER 24
#define EEPROM_LASTUSER 1024
#define NUMUSERS  ((EEPROM_LASTUSER - EEPROM_FIRSTUSER)/5)  //Define number of internal users (200 for UNO/Duemillanova)

#define DOORPIN1 relayPins[0]           // Define the pin for electrified door 1 hardware
#define DOORPIN2 relayPins[2]           // Define the pin for electrified door 2 hardware
#define ALARMSTROBEPIN relayPins[3]     // Define the "non alarm: output pin. Can go to a strobe, small chime, etc
#define ALARMSIRENPIN  relayPins[1]     // Define the alarm siren pin. This should be a LOUD siren for alarm purposes.

byte reader1Pins[]={2,3};               // Reader 1 connected to pins 4,5
byte reader2Pins[]= {4,5};              // Reader2 connected to pins 6,7

//byte reader3Pins[]= {10,11};                // Reader3 connected to pins X,Y (Not implemented on v1.x and 2.x Access Control Board)

const byte analogsensorPins[] = {0,1,2,3};    // Alarm Sensors connected to other analog pins
const byte relayPins[]= {6,7,8,9};            // Relay output pins

bool door1Locked=true;                        // Keeps track of whether the doors are supposed to be locked right now
bool door2Locked=true;

unsigned long door1locktimer=0;               // Keep track of when door is supposed to be relocked
unsigned long door2locktimer=0;               // after access granted.

boolean doorChime=false;                       // Keep track of when door chime last activated
boolean doorClosed=false;                      // Keep track of when door last closed for exit delay

unsigned long alarmDelay=0;                    // Keep track of alarm delay. Used for "delayed activation" or level 2 alarm.
unsigned long alarmSirenTimer=0;               // Keep track of how long alarm has gone off


unsigned long consolefailTimer=0;               // Console password timer for failed logins
byte consoleFail=0;
#define numUsers (sizeof(superUserList)/sizeof(long))                  //User access array size (used in later loops/etc)
#define NUMDOORS (sizeof(doorPin)/sizeof(byte))
#define numAlarmPins (sizeof(analogsensorPins)/sizeof(byte))

//Other global variables
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;     // Global RTC clock variables. Can be set using DS1307.getDate function.

byte alarmActivated = EEPROM.read(EEPROM_ALARM);                   // Read the last alarm state as saved in eeprom.
byte alarmArmed = EEPROM.read(EEPROM_ALARMARMED);                  // Alarm level variable (0..5, 0==OFF) 

boolean sensor[4]={false};                                         // Keep track of tripped sensors, do not log again until reset.
unsigned long sensorDelay[2]={0};                                  // Same as above, but sets a timer for 2 of them. Useful for logging
                                                                   // motion detector hits for "occupancy check" functions.

// Enable up to 3 door access readers.
volatile long reader1 = 0;
volatile int  reader1Count = 0;
volatile long reader2 = 0;
volatile int  reader2Count = 0;
int userMask1=0;
int userMask2=0;
boolean keypadGranted=0;                                       // Variable that is set for authenticated users to use keypad after login

//volatile long reader3 = 0;                                   // Uncomment if using a third reader.
//volatile int  reader3Count = 0;
//unsigned long tagNumber ;

// Serial terminal buffer (needs to be global)
char inString[40]={0};                                         // Size of command buffer (<=128 for Arduino)
byte inCount=0;
//boolean privmodeEnabled = false;    
// Switch for enabling "priveleged" commands
// ML changed to true
boolean privmodeEnabled = true; 


/* Create an instance of the various C++ libraries we are using.
 */

//DS1307 ds1307;        // RTC Instance
//WIEGAND26 wiegand26;  // Wiegand26 (RFID reader serial protocol) library
//PCATTACH pcattach;    // Software interrupt library

/* Set up some strings that will live in flash instead of memory. This saves our precious 2k of
 * RAM for something else.
*/

const prog_uchar rebootMessage[]          PROGMEM  = {"Access Control System rebooted."};

const prog_uchar doorChimeMessage[]       PROGMEM  = {"Front Door opened."};
const prog_uchar doorslockedMessage[]     PROGMEM  = {"All Doors relocked"};
const prog_uchar alarmtrainMessage[]      PROGMEM  = {"Alarm Training performed."};
const prog_uchar privsdeniedMessage[]     PROGMEM  = {"Access Denied. Priveleged mode is not enabled."};
const prog_uchar privsenabledMessage[]    PROGMEM  = {"Priveleged mode enabled."};
const prog_uchar privsdisabledMessage[]   PROGMEM  = {"Priveleged mode disabled."};
const prog_uchar privsAttemptsMessage[]   PROGMEM  = {"Too many failed attempts. Try again later."};

const prog_uchar consolehelpMessage1[]    PROGMEM  = {"Valid commands are:"};
const prog_uchar consolehelpMessage2[]    PROGMEM  = {"(d)ate, (s)show user, (m)odify user <num>  <usermask> <tagnumber>"};
const prog_uchar consolehelpMessage3[]    PROGMEM  = {"(a)ll user dump,(r)emove_user <num>,(o)open door <num>"};
const prog_uchar consolehelpMessage4[]    PROGMEM  = {"(u)nlock all doors,(l)lock all doors"};
const prog_uchar consolehelpMessage5[]    PROGMEM  = {"(1)disarm_alarm, (2)arm_alarm,(3)train_alarm (9)show_status"};
const prog_uchar consolehelpMessage6[]    PROGMEM  = {"(e)nable <password> - enable or disable priveleged mode"};                                       
const prog_uchar consoledefaultMessage[]  PROGMEM  = {"Invalid command. Press '?' for help."};

const prog_uchar statusMessage1[]         PROGMEM  = {"Alarm armed state (1=armed):"};
const prog_uchar statusMessage2[]         PROGMEM  = {"Alarm siren state (1=activated):"};
const prog_uchar statusMessage3[]         PROGMEM  = {"Front door open state (0=closed):"};
const prog_uchar statusMessage4[]         PROGMEM  = {"Roll up door open state (0=closed):"};     
const prog_uchar statusMessage5[]         PROGMEM  = {"Door 1 unlocked state(1=locked):"};                   
const prog_uchar statusMessage6[]         PROGMEM  = {"Door 2 unlocked state(1=locked):"}; 




void setup(){           // Runs once at Arduino boot-up

  Serial.begin(57600);	               	       // Set up Serial output at 8,N,1,57600bps
  
} //end setup




void loop()                                     // Main branch, runs over and over again
{                         

   readCommand();                                 // Check for commands entered at serial console

  
}


void PROGMEMprintln(const prog_uchar str[])    // Function to retrieve logging strings from program memory
{                                              // Prints newline after each string  
  char c;
  if(!str) return;
  while((c = pgm_read_byte(str++))){
    Serial.print(c,BYTE);
                                   }
    Serial.println();
}



void clearUsers()    //Erases all users from EEPROM
{
  for(int i=EEPROM_FIRSTUSER; i<=EEPROM_LASTUSER; i++){
    EEPROM.write(i,0);  
    
    Serial.println("User database erased.");  
  }
}

void addUser(int userNum, byte userMask, unsigned long tagNumber)       // Modifies a user an entry in the local database.
{                                                                       // Users number 0..NUMUSERS
  int offset = (EEPROM_FIRSTUSER+(userNum*5));                          // Find the offset to write this user to
  byte EEPROM_buffer[5] ={0};                                           // Buffer for creating the 4 byte values to write. Usermask is stored in byte 5.

  Serial.print("In addUser.");
  if((userNum <0) || (userNum > NUMUSERS)) {                            // Do not write to invalid EEPROM addresses.

    Serial.print("Invalid user modify attempted.");
  }
  else
  {
    Serial.print(" input tag number as unsigned long expressed in Hex  and the in binary are = ") ;
    Serial.println(tagNumber, HEX) ;
    Serial.println(tagNumber, BIN) ;
    EEPROM_buffer[0] = byte(tagNumber &  0xFFF);   // Fill the buffer with the values to write to bytes 0..4 
     Serial.println(EEPROM_buffer[0],BIN);
    EEPROM_buffer[1] = byte(tagNumber >> 8);
     Serial.println(EEPROM_buffer[1],BIN);
    EEPROM_buffer[2] = byte(tagNumber >> 16);
     Serial.println(EEPROM_buffer[2],BIN);
    EEPROM_buffer[3] = byte(tagNumber >> 24);
     Serial.println(EEPROM_buffer[3],BIN);
    EEPROM_buffer[4] = byte(userMask);

    for(int i=0; i<5; i++){
      EEPROM.write((offset+i), (EEPROM_buffer[i])); // Store the resulting value in 5 bytes of EEPROM.

    }
    Serial.print("User ");
    Serial.print(userNum,DEC);
    Serial.println(" successfully modified"); 
  }
}  //end addUser





void deleteUser(int userNum)                                            // Deletes a user from the local database.
{                                                                       // Users number 0..NUMUSERS
  int offset = (EEPROM_FIRSTUSER+(userNum*5));                          // Find the offset to write this user to
  
  if((userNum <0) || (userNum > NUMUSERS)) {                            // Do not write to invalid EEPROM addresses.

    Serial.print("Invalid user delete attempted.");
  }
  else
  {
    for(int i=0; i<5; i++){
     
      EEPROM.write((offset+i), 0xFF); // Store the resulting value in 5 bytes of EEPROM.
                                                    // Starting at offset
    }
    Serial.print("User deleted at position "); 
    Serial.println(userNum);
  }
}



int checkUser(unsigned long tagNumber)                                  // Check if a particular tag exists in the local database. Returns userMask if found.
{                                                                       // Users number 0..NUMUSERS
  // Find the first offset to check

  unsigned long EEPROM_buffer=0;                                         // Buffer for recreating tagNumber from the 4 stored bytes.
  int found=-1;
  
 //ML added the print to serial
  Serial.println("in checkUser") ;
  Serial.print("tagNumber in dec = ") ;
  Serial.println(tagNumber) ;
  Serial.print("tagNumber in BINARY = ") ;
  Serial.print(tagNumber, BIN) ; 
  
  for(int i=EEPROM_FIRSTUSER; i<=120; i=i+5){
  //for(int i=EEPROM_FIRSTUSER; i<=(EEPROM_LASTUSER-5); i=i+5){
    EEPROM_buffer=0;
    
    EEPROM_buffer=(EEPROM.read(i+3));
    Serial.print("EEPROM_buffer i+3  in binary = ") ;
    Serial.println(EEPROM_buffer, BIN) ;
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2));
    Serial.print("EEPROM_buffer i+2 in binary = ") ;
    Serial.println(EEPROM_buffer, BIN) ;
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1));
    Serial.print("EEPROM_buffer i+1 in binary = ") ;
    Serial.println(EEPROM_buffer, BIN) ;
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i));
    Serial.print("EEPROM_buffer i in binary = ") ;
    Serial.println(EEPROM_buffer, BIN) ;

    if((EEPROM_buffer == tagNumber) && (tagNumber !=0xFFFFFFFF) && (tagNumber !=0x0)) {    // Return a not found on blank (0xFFFFFFFF) entries 
      Serial.print("User ");
      Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC);
      Serial.println(" authenticated.");
      found = EEPROM.read(i+4);
      return found;
    }                             

  }
  Serial.println("User not found");
  delay(1000);                                                            // Delay to prevent brute-force attacks on reader
  return found;                        
}





void dumpUser(byte usernum)                                            // Return information ona particular entry in the local DB
{                                                                      // Users number 0..NUMUSERS

  unsigned long EEPROM_buffer=0;                                       // Buffer for recreating tagNumber from the 4 stored bytes.

  Serial.println(" in dumpUser starting ") ;
  
  if((0<=usernum) && (usernum <=8)){
  //if((0<=usernum) && (usernum <=199)){
    int i=usernum*5+EEPROM_FIRSTUSER;

    EEPROM_buffer=0;
    EEPROM_buffer=(EEPROM.read(i+3));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i));

    //Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC);
    Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC);
    Serial.print("\t");
    Serial.print(EEPROM.read(i+4),DEC);
    Serial.print("\t");

    Serial.println(" in dumpUser printing ulong eeprom_buffer ") ;
    Serial.println(EEPROM_buffer) ;
    if(DEBUG==2){
      Serial.print(" in dump user, the eeprom_buffer in hex") ;
      Serial.println(EEPROM_buffer,HEX);
                 }
     else {
           if(EEPROM_buffer != 0xFFFFFFFF) {
             Serial.print("********");}
           }
  }
  else Serial.println("Bad user number!");
}


/* Displays a serial terminal menu system for
 * user management and other tasks
 */

void readCommand() {                                               
                                                                    
//ML  this is not correct -- it is giving a (
//byte stringSize=(sizeof(inString)/sizeof(char));
//ML  I believe what is needed is
//byte stringSize = sizeof(inString) ;

char cmdString[3][9] = {0};


byte j=0;                                                          // Counters
byte k=0;
char cmd=0;

byte index = 0 ;
char ch;
//ML Added the > 0 

//set the inString to a value of q
//  not allowed to use q in the commands
for (byte i=0; i < 40; i++)
{
  inString[i] = NULL ;
}

 while (Serial.available() > 0 ) 
 {    
 // Check if user entered a command this round	 
   if (  index <   40   )
   {
     ch = Serial.read();   
     inString[index] =  ch ;    
     index++ ;
   }
  //Serial.print(ch);                        // Turns echo on or off
   Serial.println(ch) ;
 }

  if (index!= 0) 
  { //added by ML because equal zero does not go through
  //if(inCount==0) 
  Serial.println("inside index if  and index is ") ;
  Serial.println(index) ;
  
  //now break up the string
  for(byte i=0;  i< index  ; i++) 
  {
    cmdString[j][k] = inString[i];
    if(k<9) 
       k++;
    else break;
 
    if(inString[i] == ' ') // Check for space and if true, terminate string and move to next string.
    {
      cmdString[j][k-1]=0;
      if(j<3)
          j++;
      else break;
          k=0;             
    }
   }
  
 cmd = cmdString[0][0];
   Serial.println("cmd is ") ;
   Serial.println(cmd) ;
   Serial.println(" first arg is ") ;
   Serial.println(cmdString[1][0]) ;
                                       
               switch(cmd) {


                 case 'a': {                                                 
                   // List whole user database
                  
                      Serial.println(" in case statement a");             
                      //logDate();
                      Serial.println("User dump started.");
                      Serial.print("UserNum:");
                      Serial.print(" ");
                      Serial.print("Usermask:");
                      Serial.print(" ");
                      Serial.println("TagNum:");

                      for(int i=0; i<(9); i++)
                      //for(int i=0; i<(NUMUSERS); i++)
                      {
                        dumpUser(i);
                        Serial.println();
                       }   
                     break ;  
                           }

                 case 's': {                                                 // List user 
             
                     Serial.print("UserNum:");
                     Serial.print("   ");
                     Serial.print(atoi(cmdString[1])) ;
                     Serial.println();
                     //Serial.print("Usermask:");
                     //Serial.print(" ");
                     //Serial.println("TagNum:");
                     dumpUser(atoi(cmdString[1]));
                     Serial.println();  

                     break ;                     
                           }
 
                   case 'c': {
                     checkUser(strtoul(cmdString[1],NULL,16)) ;
                     //checkUser(atoi(cmdString[1])) ;
                     //Serial.println(" Check user input as string ") ;
                     //Serial.println( cmdString[1] ) ;
                    // int checkUserInput_int = atoi( cmdString[1] ) ;
                    // Serial.println(" Check user input as atoi int ") ;
                    // Serial.println(  checkUserInput_int );
                    // unsigned long checkUserInput_ul = (unsigned long) ( checkUserInput_int) ;
                    // Serial.println(" Check user input as int cast to ul ") ;
                    // Serial.println(  checkUserInput_ul );
                     break ;
                   }
                
                   case 'r': {                                                 // Remove a user
                 
                    dumpUser(atoi(cmdString[1]));
                    deleteUser(atoi(cmdString[1]));
                    break ;
                   }              

                   case 'm': {                                                                // Add/change a user                   
                 
                   Serial.println("inside m(odify)") ;
                   dumpUser(atoi(cmdString[1]));
                   addUser(atoi(cmdString[1]), atoi(cmdString[2]), strtoul(cmdString[3],NULL,16));                
                   dumpUser(atoi(cmdString[1]));
                   break ;
                  }
                     
        
          }  
  }               
}                                      // End of function 




